// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package template

import (
	"fmt"
	"text/template/parse"
)

// 上下文描述了当HTML解析器到达通过计算特定模板节点生成的HTML的
// 部分时必须处于的状态。
// 
// type context的零值是模板的起始上下文，
// 生成一个在
// www.w3.org/TR/html5/syntax.html#the-end 
// 上下文元素为空。
type context struct {
	state   state
	delim   delim
	urlPart urlPart
	jsCtx   jsCtx
	attr    attr
	element element
	n       parse.Node // for range break/continue 
	err     *Error
}

func (c context) String() string {
	var err error
	if c.err != nil {
		err = c.err
	}
	return fmt.Sprintf("{%v %v %v %v %v %v %v}", c.state, c.delim, c.urlPart, c.jsCtx, c.attr, c.element, err)
}

// eq报告两个上下文是否相等。
func (c context) eq(d context) bool {
	return c.state == d.state &&
		c.delim == d.delim &&
		c.urlPart == d.urlPart &&
		c.jsCtx == d.jsCtx &&
		c.attr == d.attr &&
		c.element == d.element &&
		c.err == d.err
}

// mangle生成一个标识符，该标识符包含一个后缀，可将其与被不同上下文损坏的模板名称区分开来。
func (c context) mangle(templateName string) string {
	// 默认上下文的损坏名称是输入模板名。
	if c.state == stateText {
		return templateName
	}
	s := templateName + "$htmltemplate_" + c.state.String()
	if c.delim != delimNone {
		s += "_" + c.delim.String()
	}
	if c.urlPart != urlPartNone {
		s += "_" + c.urlPart.String()
	}
	if c.jsCtx != jsCtxRegexp {
		s += "_" + c.jsCtx.String()
	}
	if c.attr != attrNone {
		s += "_" + c.attr.String()
	}
	if c.element != elementNone {
		s += "_" + c.element.String()
	}
	return s
}

// 状态描述了一种高级HTML解析器状态。
// 
// 它限定了元素堆栈的顶部，并扩展了HTML插入
// 模式，但也包含与
// HTML5解析算法中的任何内容都不对应的状态，因为HTML 
// 语法中的单个标记生成可能包含模板中的嵌入操作。例如，引用的
// HTML属性由
// <div title=“Hello{World}”>
// 在HTML语法中是一个标记，但在模板中跨越多个节点。
type state uint8

// go:generate stringer-type state 

const (
	// stateText是解析的字符数据。当HTML解析器的解析位置位于HTML标记、指令、注释和特殊元素体之外时，它处于
	// 这种状态。
	stateText state = iota
	// stateTag出现在HTML属性或标记结尾之前。
	stateTag
	// stateAttrName出现在属性名中。
	// 它出现在“^name^=value”中的^之间。
	stateAttrName
	// stateAfterName出现在属性名称结束后，但在任何
	// 等号之前。它发生在'name^^=value'中的^之间。
	stateAfterName
	// stateBeforeValue出现在等号之后，但在值之前。
	// 它出现在'name=^^value'中的^之间。
	stateBeforeValue
	// stateHTMLCmt发生在<！--HTML注释-->。
	stateHTMLCmt
	// stateRCDATA出现在RCDATA元素（<textarea>或<title>）中
	// 如https:
	stateRCDATA
	// 所述，stateAttr出现在内容为文本的HTML属性中。
	stateAttr
	// stateURL出现在内容为URL的HTML属性中。
	stateURL
	// stateSrcset出现在HTML srcset属性中。
	stateSrcset
	// stateJS发生在事件处理程序或脚本元素中。
	stateJS
	// stateJSDqStr出现在JavaScript双引号字符串中。
	stateJSDqStr
	// stateJSSqStr出现在JavaScript单引号字符串中。
	stateJSSqStr
	// stateJSRegexp出现在JavaScript regexp文本中。
	stateJSRegexp
	// stateJSBlockCmt出现在JavaScript/*块注释*/中。
	stateJSBlockCmt
	// stateJSLineCmt出现在JavaScript中
	stateJSLineCmt
	// stateCSS出现在<style>元素或style属性中。
	stateCSS
	// stateCSSDqStr出现在CSS双引号字符串中。
	stateCSSDqStr
	// stateCSSQSTR出现在CSS单引号字符串中。
	stateCSSSqStr
	// stateCSSDqURL出现在CSS双引号url（“…”）中。
	stateCSSDqURL
	// stateCSSQURL出现在CSS单引号url（“…”）中。
	stateCSSSqURL
	// stateCSurl出现在CSS无引号的url（…）中。
	stateCSSURL
	// stateCSSBlockCmt出现在CSS/*块注释*/中。
	stateCSSBlockCmt
	// stateCslinecmt发生在CSS内部
	stateCSSLineCmt
	// stateError是任何有效
	// HTML/CSS/JS构造之外的一种传染性错误状态。
	stateError
	// stateDead在{break}或{continue}之后标记无法访问的代码。
	stateDead
)

// isComment适用于包含模板内容的任何状态
// 作者和维护者，而非最终用户或机器。
func isComment(s state) bool {
	switch s {
	case stateHTMLCmt, stateJSBlockCmt, stateJSLineCmt, stateCSSBlockCmt, stateCSSLineCmt:
		return true
	}
	return false
}

// isInTag返回s是否仅出现在HTML标记中。
func isInTag(s state) bool {
	switch s {
	case stateTag, stateAttrName, stateAfterName, stateBeforeValue, stateAttr:
		return true
	}
	return false
}

// delim是结束当前HTML属性的分隔符。
type delim uint8

// go:generate stringer-type delim 

const (
	// delimNone出现在任何属性之外。
	delimNone delim = iota
	// DelimSpaceOrtagen在双引号（“）关闭属性时发生。
	delimDoubleQuote
	// delimSingleQuote在单引号（'）关闭属性时发生。
	delimSingleQuote
	// DelimSpaceOrtagen在空格或直角括号（>）时发生。
	// 关闭属性。
	delimSpaceOrTagEnd
)

// urlPart标识RFC 3986分层URL中的一个部分，以允许不同的
// 编码策略。
type urlPart uint8

// go:generate stringer-type urlPart 

const (
	// urlPartNone在不在URL中时发生，或者可能在开始时：
	// /^in“^http:
	urlPartNone urlPart = iota
	// urlPartPreQuery出现在scheme、authority或path中；
	// /^s之间在“h^ttp:
	urlPartPreQuery
	// urlPartQueryOrFrag出现在
	// “http:
	urlPartQueryOrFrag
	// urlPartUnknown是由于在查询分隔符之前和之后连接了上下文而发生的。
	urlPartUnknown
)

// jsCtx确定“/”是启动正则表达式文本还是
// ”除法运算符。
type jsCtx uint8

// go:generate stringer-type jsCtx 

const (
	// jsCtxRegexp发生在“/”将启动regexp的地方字面意义的
	jsCtxRegexp jsCtx = iota
	// jsCtxDivOp发生在“/”将启动除法运算符的位置。
	jsCtxDivOp
	// jsCtxUnknown出现在“/”由于上下文连接而不明确的地方。
	jsCtxUnknown
)

// 元素在开始标记或特殊体中标识HTML元素。
// 某些HTML元素（例如<script>和<style>）的主体是
// 不同于stateText的处理方式，因此必须使用元素类型来将
// 转换为标记末尾的正确上下文，并标识主体的
// 结束分隔符。
type element uint8

// go:generate stringer-type元素

const (
	// elementNone出现在特殊标记或特殊元素体之外。
	elementNone element = iota
	// elementScript对应于原始文本<script>元素
	// 带有JS MIME类型或无类型属性。
	elementScript
	// elementStyle对应于原始文本<style>元素。
	elementStyle
	// elementTextarea对应于RCDATA<textarea>元素。
	elementTextarea
	// elementTitle对应于RCDATA<title>元素。
	elementTitle
)

// go:generate stringer-type attr 

// attr在属性中标识当前HTML属性，即从stateAttrName开始，直到stateTag/stateText（独占）。
type attr uint8

const (
	// attrNone对应正常属性或无属性。
	attrNone attr = iota
	// attrScript对应于事件处理程序属性。
	attrScript
	// attrScriptType对应于脚本HTML元素中的type属性
	attrScriptType
	// attrStyle对应于值为CSS的style属性。
	attrStyle
	// attrURL对应于一个值为URL的属性。
	attrURL
	// attrscscret对应于一个srcset属性。
	attrSrcset
)
